package com.googlecode.connectlet.crypto.stream;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;

public abstract class StreamCipher implements Serializable {
	private static final long serialVersionUID = 1L;

	public abstract void reset();
	public abstract int output();

	public byte[] output(int len) {
		byte[] b = new byte[len];
		output(b, 0, len);
		return b;
	}

	public void output(byte[] b) {
		output(b, 0, b.length);
	}

	public void output(byte[] b, int off, int len) {
		for (int i = 0; i < len; i ++) {
			b[off + i] = (byte) output();
		}
	}

	public int update(int b) {
		return b ^ output();
	}

	public byte[] update(byte[] b) {
		return update(b, 0, b.length);
	}

	public byte[] update(byte[] b, int off, int len) {
		byte[] result = new byte[len];
		update(b, off, result, 0, len);
		return result;
	}

	public void update(byte[] src, int srcPos, byte[] dest, int destPos, int length) {
		for (int i = 0; i < length; i ++) {
			dest[destPos + i] = (byte) update(src[srcPos + i]);
		}
	}

	public OutputStream encrypt(final OutputStream out) {
		return new OutputStream() {
			@Override
			public void write(int b) throws IOException {
				out.write(update(b));
			}

			@Override
			public void write(byte[] b, int off, int len) throws IOException {
				out.write(update(b, off, len));
			}

			@Override
			public void flush() throws IOException {
				out.flush();
			}

			@Override
			public void close() throws IOException {
				out.close();
			}
		};
	}

	public InputStream decrypt(final InputStream in) {
		return new InputStream() {
			@Override
			public int read() throws IOException {
				int b = in.read();
				return b < 0 ? b : update(b) & 0xff;
			}

			@Override
			public int read(byte[] b, int off, int len) throws IOException {
				byte[] bb = new byte[len];
				int bytesRead = in.read(bb, 0, len);
				update(bb, 0, b, off, bytesRead);
				return bytesRead;
			}

			@Override
			public int available() throws IOException {
				return in.available();
			}

			@Override
			public void close() throws IOException {
				in.close();
			}
		};
	}
}